import * as cc from 'cc';
const { ccclass, property } = cc._decorator;

interface API {
    play(
        platformId: number,
        clip: cc.AudioClip,
        loop: boolean,
        volume: number,
    ): void;
    stop(platformId: number): void;
    pause(platformId: number): void;
    resume(platformId: number): void;
    isPlaying(platformId: number): boolean;
    setVolume(platformId: number, volume: number): void;
    getVolume(platformId: number): number;
    // 可选：释放底层资源
    release?(platformId: number): void;
}

class CC_API implements API {
    constructor(public node: cc.Node) {}

    private nodePool = new cc.NodePool('audio-node');
    private clipIdMap = new Map<number, cc.AudioSource>();

    private getNode(name: string): cc.Node {
        let node = this.nodePool.get();
        if (!node) {
            node = new cc.Node(name);
            node.layer = cc.Layers.BitMask.NONE;
        }
        node.active = true;
        node.parent = this.node;
        node.addComponent(cc.AudioSource);
        return node;
    }

    private getOrCreateSource(
        id: number,
        clip: cc.AudioClip,
    ): cc.AudioSource {
        let source = this.clipIdMap.get(id);
        if (source) return source;

        const node = this.getNode(`audio-${id}-${clip.name || 'clip'}`);
        source = node.getComponent(cc.AudioSource)!;
        source.clip = clip;
        this.clipIdMap.set(id, source);
        return source;
    }

    private getSource(id: number): cc.AudioSource | undefined {
        let source = this.clipIdMap.get(id);
        return source;
    }

    play(
        platformId: number,
        clip: cc.AudioClip,
        loop: boolean,
        volume: number,
    ) {
        const audioSource = this.getOrCreateSource(platformId, clip);
        audioSource.currentTime = 0;
        audioSource.loop = loop;
        audioSource.volume = volume;
        audioSource.play();
    }
    stop(_: number): void {
        const source = this.getSource(_);
        if (!source) return;
        source.stop();
        this.release(_);
    }
    pause(_: number): void {
        const source = this.getSource(_);
        if (!source) return;
        source.pause();
    }
    resume(_: number): void {
        const source = this.getSource(_);
        if (!source) return;
        source.play();
    }
    isPlaying(_: number): boolean {
        const source = this.getSource(_);
        if (!source) return false;
        return source.playing;
    }
    setVolume(_: number, volume: number): void {
        const source = this.getSource(_);
        if (!source) return;
        source.volume = volume;
    }
    getVolume(_: number): number {
        const source = this.getSource(_);
        if (!source) return 0;
        return source.volume;
    }
    release(id: number) {
        const source = this.getSource(id);
        if (source) {
            source.stop();
            source.clip = null;
            source.node.active = false;
            source.node.parent = null;
            this.nodePool.put(source.node);
        }
        this.clipIdMap.delete(id);
    }
}

interface AudioData {
    id: number;
    clip: cc.AudioClip;
    loop: boolean;
    volume: number;
    checker?: () => void; // 用于停止轮询
    onEnded?: (id: number) => void;
}

@ccclass('AudioEffectManager')
export class AudioEffectManager extends cc.Component {
    @property({
        type: cc.CCInteger,
        tooltip: '同一个音频最大可以播放次数',
    })
    maxClip: number = 3;

    @property({
        type: cc.CCInteger,
        tooltip: '全部音频最大可以播放次数',
    })
    maxAllClip: number = 15;

    // 可替换为项目中真实实现
    private api!: API;

    // 自增 manager id（对外返回），与底层 platformId 分离
    private nextId = 1;

    // manager id -> 播放信息
    private playingMap = new Map<number, AudioData>();

    // clip -> 按播放顺序的 manager id 队列（用于统计并发并可找到最早）
    private clipPlayingQueue = new Map<cc.AudioClip, number[]>();

    // 全局 FIFO 队列（最早的在前）
    private fifoQueue: number[] = [];

    private static instance: AudioEffectManager = null!;
    public static get Instance() {
        return AudioEffectManager.instance;
    }

    protected onLoad(): void {
        this.api = new CC_API(this.node);
        AudioEffectManager.instance = this;
    }

    /**
     * 播放音效
     * @param clip 已加载的 cc.AudioClip
     * @param onEnded 可选，播放结束回调，参数为 manager id
     * @param volume 可选，0..1 相对音量（默认 1）
     * @returns manager id（>0 表示成功，0 表示失败）
     */
    play(
        clip: cc.AudioClip,
        onEnded?: (id: number) => void,
        volume: number = 1,
    ): number {
        if (!clip) return 0;

        // 先确保总并发不超过 maxAllClip：如超过则停止最早的播放以释放槽位
        if (this.fifoQueue.length >= this.maxAllClip) {
            const oldestId = this.fifoQueue.shift();
            if (oldestId !== undefined) this.stop(oldestId);
        }

        // 再确保单 clip 并发不超过 maxClip：如超过则停止该 clip 最早的一个
        const clipQueue = this.clipPlayingQueue.get(clip) ?? [];
        if (clipQueue.length >= this.maxClip) {
            const oldestForClip = clipQueue.shift();
            if (oldestForClip !== undefined) this.stop(oldestForClip);
        }
        const id = this.nextId++;
        // 调用底层播放 API（由 CC_API 实现）
        this.api.play(id, clip, false, volume);
        const entry: AudioData = {
            id,
            clip,
            loop: false,
            volume,
            checker: () => {},
            onEnded,
        };

        // 记录
        this.playingMap.set(id, entry);
        // clip queue
        const q = this.clipPlayingQueue.get(clip) ?? [];
        q.push(id);
        this.clipPlayingQueue.set(clip, q);
        // fifo
        this.fifoQueue.push(id);

        // 轮询检查播放结束（挂载到 this.node 的 schedule）
        const checker = () => {
            const e = this.playingMap.get(id);
            if (!e) {
                // 已被清理
                this.unschedule(checker);
                return;
            }
            let playing = false;
            playing = this.api.isPlaying(e.id);
            if (!playing) {
                // 播放结束，清理并触发回调/事件
                this.cleanupAfterEnd(id);
                try {
                    e.onEnded?.(id);
                    this.node.emit('audio-ended', id);
                } catch (err) {
                    console.error(err);
                }
                this.unschedule(checker);
            }
        };
        entry.checker = checker;
        this.schedule(checker, 0.1);

        return id;
    }

    pause(id: number) {
        const e = this.playingMap.get(id);
        if (!e) return;
        this.api.pause(e.id);
    }

    resume(id: number) {
        const e = this.playingMap.get(id);
        if (!e) return;
        this.api.resume(e.id);
    }

    stop(id: number) {
        const e = this.playingMap.get(id);
        if (!e) return false;
        this.api.stop(id);
        this.cleanupAfterEnd(id);
    }

    isPlaying(id: number): boolean {
        const e = this.playingMap.get(id);
        if (!e) return false;
        return this.api.isPlaying(e.id);
    }

    pauseAll() {
        for (const [id, e] of this.playingMap.entries()) {
            this.pause(e.id);
        }
    }

    resumeAll() {
        for (const [id, e] of this.playingMap.entries()) {
            this.resume(e.id);
        }
    }

    stopAll() {
        // clone keys to avoid mutation during iteration
        const ids = Array.from(this.playingMap.keys());
        for (const id of ids) this.stop(id);
    }

    /**
     * 按 manager id 设置该播放实例音量（0..1）
     */
    setVolume(id: number, value: number) {
        const e = this.playingMap.get(id);
        if (!e) return;
        value = Math.max(0, Math.min(1, value));
        e.volume = value;
        this.api.setVolume(e.id, value);
    }

    getVolume(id: number): number {
        const e = this.playingMap.get(id);
        if (!e) return 0;
        return this.api.getVolume(e.id);
    }

    // ---------- 内部帮助方法 ----------
    private cleanupAfterEnd(id: number) {
        const e = this.playingMap.get(id);
        if (!e) return;
        // 取消轮询
        if (e.checker) {
            try {
                this.unschedule(e.checker);
            } catch (err) {
                console.error(err);
            }
        }
        // 从 playingMap 删除
        this.playingMap.delete(id);
        // 从 clip queue 删除
        const clipQ = this.clipPlayingQueue.get(e.clip);
        if (clipQ) {
            const idx = clipQ.indexOf(id);
            if (idx >= 0) clipQ.splice(idx, 1);
            if (clipQ.length === 0) this.clipPlayingQueue.delete(e.clip);
            else this.clipPlayingQueue.set(e.clip, clipQ);
        }
        // 从 fifo 删除
        const fi = this.fifoQueue.indexOf(id);
        if (fi >= 0) this.fifoQueue.splice(fi, 1);
        // 如有需要，释放底层资源
        this.api.release?.(e.id);
    }
}
